--data
require "data.users"

--server connections
server = {
host = 0,
port = 0,
connection = "not valid token",
data = {}, --place wehre we store all our data
history = {},
token = "broker00001",
timeout = 1000,
viewhistory = function(self)
  for k,v in ipairs(self.history) do
    print(k,v)
  end
end,
connect = function(self, host,port)
  self.connection = assert(socket.tcp())
  self.host = host or "localhost"
  self.port = port or 222
  assert(self.connection:connect(self.host,self.port))
  assert(self.connection:setoption('keepalive',true))
  assert(self.connection:settimeout(self.timeout))
  self.token = self:send({cmd="connect"})
  return self.token
end,
sendlua = function(self,data)
  local s = marshall('data',data,";")
  table.insert(self.history,s)
  self.connection:send(string.format("%s\n",s))
end,
sendjson = function(self,data)
  local s = "json"..json.encode(data)
  table.insert(self.history,s)
  self.connection:send(string.format("%s\n",s))
end,
send    = function(self,data)
  self:sendlua(data)
  return self:receive()
end,
receive = function(self)
  return self.connection:receive("*l") --store my connection token
end,
cmd    = function(self,what,data)
  data.cmd    = what
  data.token  = self.token --broker token
  return self:send(data)
end,
disconnect = function(self)
  local byebye = self:send({cmd="disconnect"})
  self.connection:shutdown("both")
  self.connection:close()
  return byebye
end,
}

--Decode data sent as lua or json
function decode(s)
  local t = none
  if (string.sub(s, 1, 4) == "json") then --json object   
     t = json.decode(s,5)--don't parse the word "json"
  else
     t = unmarshall(s)
  end
  return t
end  

--Handle incomming data
function handler(data,session) -->all incomming data will pass through this filter
    if (data == "shutdown") then
      return false, "shutdown"
    else
      local reply = "ok"
      local d = decode(data)
      local cmd = ""
      print(session)
      if type(d)=="table" then
        print('data:')
        print(marshall('data',d))--print the table i got
        cmd = d["cmd"] 
      else
        print('data:',d)
        cmd = d
      end
      local ok
      ok, reply = pcall(rpc[cmd],d)
      if ok then
        --hanlded rpc
        printf("handled rpc: [%s:%s]",cmd,reply)
      else
        --unhandled rpc
        printf("unhandled rpc: [%s:%s]",cmd,reply)
      end  
      return true, reply
    end
end

--table for temporary connection tokens
tokens = {}

--remote procedure call table
rpc = {}
rpc.connect = function(data)
  print("connect")
  local token = createtoken() --create a token for this user
  tokens[token] = token --store the user id
  return token --return token to user for login
end
rpc.login =function(data)
  local token  = data["token"]  --get the agreed upon token
  local user   = data["user"]   --get the username
  local secret = data["secret"] --get encrypted password, salted with token 
  if (encrypt(token..users[user])==secret) then
     --get id from server
     local uid = server:send({event = "onlogin", user= user })
     --create a persona for this user
     personas:create(token,{user=user,client=data["client"],uid = uid})
     return "logged on"
  end
  return "login failed"
end
rpc.logoff =function(data)
  print("logoff")
  local token = data["token"]
  --todo we need to avoid having people log others off if they know the token
  personas:delete(token)
  tokens[token] = nil --destroy token
  return "logoff"
end
rpc.disconnect = function(data)
  print("disconnect")
  return rpc.logoff(data)
end
rpc.play = function(data)
  local persona = personas[data.token]
  local ok, reply = pcall(persona.play,persona,data)
  if ok then
    print("handled")
  end
  return reply or "client can't play"
end
rpc.inform = function(data)
  local persona = personas[data.token]
  local ok, reply = pcall(persona.listen,persona,data)
  if ok then
    print("handled")
  end
  return reply or "client can't listen"
end
rpc.update = function(data)
  local persona = personas[data.token]
  local ok, reply = pcall(persona.update,persona,data)
  if ok then
    print("handled")
  end
  return reply or "client can't update"  
end

persona_mt = {
__type="persona",
play = function(self,data)
  --from client
  table.insert(self.history,data)  --remember all actions a user has made, for metrics
  self.intents[data.action] = data --if a user sends several overlapping 
                                   --actions within a round they overwrite
  return "played"
end,
listen  = function(self,data)
  --from server
  return "don't care"
end,
update  = function(self,data)
  --from client
  return marshall('view',self.myview,";")
end,
}

persona = function(args)
  local p = {}
  setmetatable(p,{__index = function(t,k)
                     return persona_mt[k]
                  end,})
  p.history = {}
  p.intents = {}
  p.myview  = {time=now,a={},b={}}
  p.user  = args.user
  p.uid   = args.uid
  return p
end

personas = {
  create = function(self, token, args)
    self[token] = persona(args)
  end,
  delete = function(self, token, args)
    self[token] = nil
  end,
}

--Main entrypoint
function main(args)
  local args = args or {}
  --try to connect to server
  server:connect()
  
  --setup my service
  myservice = {
    name      = "broker",
    hndserver = none, -->The service will assign a handler here
    ip        = args.host or "localhost",
    port      = args.port or 111,
    frequency = 1, -->seconds
    breakpattern = nil,
    filter    = handler
  }
  ---loop 
  service.service(myservice)
  --
  
  server:disconnect()
  return 1
end

